package model.management;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import model.entity.container.ProductContainer;
import model.entity.container.ProductGroup;
import model.entity.container.StorageUnit;
import model.exceptions.InvalidNameException;

import common.Result;

/**
 * Track the instance of the list of storage units
 * 
 * @author Ian Ryu, Ryan Cheatham
 * 
 *         {@invariant storageUnits.size() >= 0}
 */
public enum StorageUnitManager implements Serializable
{
  INSTANCE;

  private Set<StorageUnit> storageUnits = new HashSet<StorageUnit>();

  /**
   * Validates whether the given storage unit can be added to the database.
   * 
   * @param unit
   *          The storage unit to validate.
   * 
   * @return a result, whose status indicates whether the given storage unit can be added to the
   *         database.
   * 
   *         {@pre unit != null} {@post retval != null}
   */
  public static Result validateAdd(StorageUnit unit)
  {
    return INSTANCE._validateAdd(unit);
  }

  // @formatter:off
  /**
   * Attempts to add the given storage unit is added to the database.
   * 
   * @param unit
   *          The unit to be added to the database.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre unit != null}
   * 
   *         {@post unit is in the database} {@post retval != null}
   */
  // @formatter:on
  public static Result add(StorageUnit unit)
  {
    return INSTANCE._add(unit);
  }

  // @formatter:off
  /**
   * Validates whether a StorageUnit can be removed from the database.
   * 
   * @param unit
   *          The storage unit to validate the removal of.
   * @return a result, indicating whether the StorageUnit can be removed or not.
   * 
   *         {@pre unit != null}
   * 
   *         {@post calling {@link #remove(StorageUnit)} will succeed} {@post retval != null}
   */
  // @formatter:on
  public static Result validateRemove(StorageUnit unit)
  {
    return INSTANCE._validateRemove(unit);
  }

  // @formatter:off
  /**
   * Attempts to remove the given storage unit from the database.
   * 
   * @param unit
   *          The unit to be removed from the database.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre unit != null}
   * 
   *         {@post the unit is no longer in the database} {@post retval != null}
   */
  // @formatter:on
  public static Result remove(StorageUnit unit)
  {
    return INSTANCE._remove(unit);
  }

  // @formatter:off
  /**
   * Validates whether the given storage unit can be updated to the new storage unit.
   * 
   * @param before
   *          The storage unit before the update.
   * @param after
   *          The storage unit after the update.
   * 
   * @return a result, whose status indicates whether the given storage unit can be updated.
   * 
   *         {@pre before != null} {@pre after != null}
   * 
   *         {@post retval != null}
   */
  // @formatter:on
  public static Result validateUpdate(StorageUnit before, StorageUnit after)
  {
    return INSTANCE._validateUpdate(before, after);
  }

  // @formatter:off
  /**
   * Attempts to update the given storage unit.
   * 
   * @param before
   *          The storage unit before the update.
   * @param after
   *          The storage unit after the update.
   * 
   * @return a result, indicating whether this operation was successful or not.
   * 
   *         {@pre before != null} {@pre after != null}
   * 
   *         {@post the storage unit is updated in the database} {@post retval != null}
   */
  // @formatter:on
  public static Result update(StorageUnit before, StorageUnit after)
  {
    return INSTANCE._update(before, after);
  }

  /**
   * Clears all StorageUnit data
   */
  public static void clear()
  {
    INSTANCE.storageUnits.clear();
  }

  /**
   * @return An iterator of all <code>Product</code>s in the system.
   * 
   *         {@post retval != null}
   */
  public static Iterator<StorageUnit> getStorageUnits()
  {
    return INSTANCE._getStorageUnits();
  }

  private Result _validateAdd(StorageUnit unit)
  {
    if (storageUnits.contains(unit))
      return new Result(false, "Cannot add StorageUnit - one already exists with the same name!");
    else
      return new Result(true);
  }

  private Result _add(StorageUnit unit)
  {
    Result result = _validateAdd(unit);
    if (result.getStatus() == false)
      return result;
    else
    {
      storageUnits.add(unit);
      return result;
    }
  }

  private Result _validateRemove(StorageUnit unit)
  {
    Iterator<ProductGroup> groups = unit.getProductGroups();
    while (groups.hasNext())
    {
      ProductGroup group = groups.next();
      if (!ProductGroupManager.validateRemoveProductGroup(group).getStatus())
        return new Result(false,
            "Cannot remove StorageUnit - cannot remove a child product group!");
    }

    if (unit.getItems().hasNext()) // unit has items
      return new Result(false, "Cannot remove StorageUnit - unit contains items!");

    return new Result(true);
  }

  private Result _remove(StorageUnit unit)
  {
    Result result = validateRemove(unit);
    if (result.getStatus() == false)
      return result;
    else
    {
      storageUnits.remove(unit);
      return new Result(true);
    }
  }

  private Result _validateUpdate(StorageUnit before, StorageUnit after)
  {
    if (before.equals(after))
      return new Result(true, "Pointless update - the before and after units are equal!");
    if (!storageUnits.contains(before))
      return new Result(false, "Cannot update StorageUnit - before instance does not exist!");
    if (storageUnits.contains(after))
      return new Result(false, "Cannot update StorageUnit - after instance already exists!");
    if (!ProductContainer.isValidName(after.getName()))
      return new Result(false, "Cannot update StorageUnit - after name is not valid!");

    return new Result(true);
  }

  private Result _update(StorageUnit before, StorageUnit after)
  {
    Result result = validateUpdate(before, after);
    if (result.getStatus() == false)
      return result;
    else
    {
      try
      {
        before.setName(after.getName());
      }
      catch (InvalidNameException e)
      {
        // the name has already been validated, so this will never call. If it does, something
        // weird happened...
        assert false;
      }
      return new Result(true);
    }
  }

  private Iterator<StorageUnit> _getStorageUnits()
  {
    return Collections.unmodifiableSet(storageUnits).iterator();
  }
}
